package flexo.input;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.lwjgl.LWJGLException;
import org.lwjgl.input.*;

import flexo.common.LogMsgType;
import flexo.input.entity.*;
import flexo.input.entity.Cursor;

public class InputSystem extends flexo.common.System
{
	public static final String ID = "input";
	
	/**
	 * The name|event assigned by the user.
	 */
	protected ArrayList<Event> eventList;
	
	protected List<EventListener> listeners;
	
	public InputSystem()
	{
		super(ID,flexo.common.System.UPDATE_STANDARD);
		flexo.Kernel.log("flexo.input.InputSystem", "Starting input module");
		eventList = new ArrayList<Event>();
		listeners = new LinkedList<EventListener>();
		
		try
		{
			Controllers.create();
		} catch (LWJGLException e)
		{
			flexo.Kernel.log("flexo.input.InputSystem", "Unable to initialize the joystick support!", LogMsgType.WARNING);
		}
		if (!flexo.Kernel.DEBUG)
		{
			Mouse.setGrabbed(true);
		}
	}
	
	public void addEventListener(EventListener l)
	{
		listeners.add(l);
	}
	
	public void removeEventListener(EventListener l)
	{
		listeners.remove(l);
	}
	
	public void setJoystickDeadZone(float dz)
	{
		for (int i = 0; i < Controllers.getControllerCount(); i++)
		{
			Controller c = Controllers.getController(i);
			for (int j = 0; j < c.getAxisCount(); ++j )
			{
				c.setDeadZone(j, dz);
			}
		}
	}	
	
	/**
	 * Adds an event to keep track of it. An event can have more than one actions.
	 * For example, you can add the event "walk_forward" with the 'keyboard_W' keyboard 
	 * event and with the 'mouse_button_1' mouse event.
	 * 
	 * You can assign one event to more than one actions but an action only can
	 * be associated with one event.
	 * 
	 * @param action The name of the event you'll receive.
	 * @param event The event that will fire the event.
	 */	
	public void addAction(String action, int event)
	{
		addAction(action,event,false);
	}
	
	public boolean existsAction(String action, int event)
	{
		Event temp = new Event(action,event,false);
		boolean contains = false;
		
		for (Event e: eventList)
		{
			if (e.compareTo(temp) == 0)
			{
				contains = true;
			}
		}
		return contains;
	}
	
	public void addAction(String action, int event, boolean repeat)
	{				
		if (!existsAction(action,event))
		{
			eventList.add(new Event(action,event,repeat));
			flexo.Kernel.log("flexo.input.InputSystem", "Created action '"+action+"'");
		}
		else
		{
			flexo.Kernel.log("flexo.input.InputSystem", "'"+action+"' action already exists", LogMsgType.WARNING);
		}
	}
	
	/**
	 * Removes all events with that action.
	 */
	public void removeAction(String name)
	{
		Iterator<Event> iter = eventList.iterator();
		while (iter.hasNext())
		{
			Event e = iter.next();
			if (e.action.equals(name))
			{
				iter.remove();
			}
		}
	}	
	
	protected void processEvent(Event e)
	{
		for (EventListener l: listeners)
		{
			l.processEvent(e);
		}
	}
	
	@Override
	public void update()
	{
		checkKeyboardEvents();
		checkMouseEvents();
		checkJoystickEvents();
		
		// Events with repeat flag on (like a repeated keypress)
		for (Event a: eventList)
		{
			if (a.repeat() && a.value > 0)
			{
				processEvent(a);
			}
		}
	}
	
	protected void tryToProcess(int event, float value)
	{
		tryToProcess(event,value,0);
	}
	
	protected void tryToProcess(int event, float value, int id)
	{
		for (Event e: eventList)
		{
			if (e.event == event)
			{
				e.setValue(value);
				e.setID(id);
				processEvent(e);
			}
		}
	}

	protected void checkKeyboardEvents()
	{
		int event = -1;
		float value = 0;
		
		// while there are events
		while(Keyboard.next())
		{
			event = Keyboard.getEventKey();
			// There's an event associated with the event
			if (Keyboard.getEventKeyState())
			{
				value = 1.0f;
			}
			else
			{
				value = 0.0f;
			}
			
			tryToProcess(event,value);
		}				
	}
	
	protected void checkMouseEvents()
	{
		int event = -1;
		float value = 0;
		
		while(Mouse.next())
		{	
			// Mouse button event
			if (Mouse.getEventButton() == 0) event = EventType.MOUSE_LBUTTON;
			if (Mouse.getEventButton() == 1) event = EventType.MOUSE_RBUTTON;
			if (Mouse.getEventButton() == 2) event = EventType.MOUSE_MBUTTON;			
			if (Mouse.getEventButtonState()) value = 1.0f;
			
			tryToProcess(event,value);
			
			event = -1; value = 0;
			
			// Mouse wheel event
			if (Mouse.getEventDWheel() > 0) event = EventType.MOUSE_WHEEL_UP;
			if (Mouse.getEventDWheel() < 0) event = EventType.MOUSE_WHEEL_DOWN;
			
			tryToProcess(event,value);
											
			event = -1; value = 0;
			
			// X movement event			
			if (Mouse.getEventDX() > 0){ event = EventType.MOUSE_X_POS; value = (float)Mouse.getEventDX();}
			if (Mouse.getEventDX() < 0){ event = EventType.MOUSE_X_NEG; value = (float)-Mouse.getEventDX();} 					
			
			tryToProcess(event,value);
			
			event = -1; value = 0;
			
			// Y movement event			
			if (Mouse.getEventDY() > 0){ event = EventType.MOUSE_Y_POS; value = (float)Mouse.getEventDY();}
			if (Mouse.getEventDY() < 0){ event = EventType.MOUSE_Y_NEG; value = (float)-Mouse.getEventDY();}
			
			tryToProcess(event,value);
			
			event = -1; value = 0;			
		}
	}
	
	protected void checkJoystickEvents()
	{
		int event = -1;
		float value = 0;
		int hwdID = -1;
		
		Controller control = null;
		
		while(Controllers.next())
		{
			hwdID = Controllers.getEventControlIndex();
			control = Controllers.getEventSource();					

			// Axis event				
			if (Controllers.isEventAxis())
			{
				int axisIndex = Controllers.getEventControlIndex();
				switch(axisIndex)
				{
					case 0:
					{
						// X Axis
						event = -1;	value = 0;
						value = control.getXAxisValue();
						if (value > 0){ event = EventType.JOY_AXIS_X_POS; }
						if (value < 0){ value = -value; event = EventType.JOY_AXIS_X_NEG; }
						tryToProcess(event,value,hwdID);
					}break;
					case 1:
					{
						// Y Axis
						event = -1;	value = 0;
						value = control.getYAxisValue();
						if (value > 0){ event = EventType.JOY_AXIS_Y_POS; }
						if (value < 0){ value = -value; event = EventType.JOY_AXIS_Y_NEG; }
						tryToProcess(event,value,hwdID);
					}break;
					case 2:
					{
						// Z Axis
						event = -1;	value = 0;
						value = control.getZAxisValue();
						if (value > 0){ event = EventType.JOY_AXIS_Z_POS; }
						if (value < 0){ value = -value; event = EventType.JOY_AXIS_Z_NEG; }
						tryToProcess(event,value,hwdID);
					}break;
					
					case 3:
					{
						// RX Axis
						event = -1;	value = 0;
						value = control.getRXAxisValue();
						if (value > 0){ event = EventType.JOY_AXIS_RX_POS; }
						if (value < 0){ value = -value; event = EventType.JOY_AXIS_RX_NEG; }
						tryToProcess(event,value,hwdID);
					}break;
					
					case 4:
					{
						// RY Axis
						event = -1;	value = 0;
						value = control.getRYAxisValue();
						if (value > 0){ event = EventType.JOY_AXIS_RY_POS; }
						if (value < 0){ value = -value; event = EventType.JOY_AXIS_RY_NEG; }
						tryToProcess(event,value,hwdID);
					}break;
					
					case 5:
					{
						// RZ Axis
						event = -1;	value = 0;
						value = control.getRZAxisValue();
						if (value > 0){ event = EventType.JOY_AXIS_RZ_POS; }
						if (value < 0){ value = -value; event = EventType.JOY_AXIS_RZ_NEG; }
						tryToProcess(event,value,hwdID);
					}break;
				}
			}
			// Button event
			event = -1;	value = 0;
			if (Controllers.isEventButton())
			{
				switch(Controllers.getEventControlIndex())
				{
					case 0: event = EventType.JOY_BUTTON_1; break;
					case 1: event = EventType.JOY_BUTTON_2; break;
					case 2: event = EventType.JOY_BUTTON_3; break;
					case 3: event = EventType.JOY_BUTTON_4; break;
					case 4: event = EventType.JOY_BUTTON_5; break;
					case 5: event = EventType.JOY_BUTTON_6; break;
					case 6: event = EventType.JOY_BUTTON_7; break;
					case 7: event = EventType.JOY_BUTTON_8; break;
					case 8: event = EventType.JOY_BUTTON_9; break;
					case 9: event = EventType.JOY_BUTTON_10; break;
					case 10: event = EventType.JOY_BUTTON_11; break;
					case 11: event = EventType.JOY_BUTTON_12; break;					
				}
				if (control.isButtonPressed(Controllers.getEventControlIndex()))
				{
					value = 1.0f;
				}
				tryToProcess(event,value,hwdID);
			}
			// POV Event
			event = -1;	value = 0;
			if (Controllers.isEventPovX())
			{
				value = control.getPovX();
				if (value > 0){ event = EventType.JOY_POV_RIGHT; }
				if (value < 0){ event = EventType.JOY_POV_LEFT; }
				tryToProcess(event,value,hwdID);
			}
			event = -1;	value = 0;
			if (Controllers.isEventPovY())
			{
				value = control.getPovY();
				if (value > 0){ event = EventType.JOY_POV_UP; }
				if (value < 0){ event = EventType.JOY_POV_DOWN; }
				tryToProcess(event,value,hwdID);
			}
		}
	}
	
	public Entity addExitOnEsc()
	{
		return new ExitOnEsc();
	}
	
	public FPSMovement addFPSMovement(flexo.scene.Node target)
	{
		return new FPSMovement(target);
	}
	
	public FPSLook addFPSLook()
	{
		return new FPSLook();
	}
	
	public Cursor addCursor()
	{
		return new Cursor();
	}
}